/**
 *    Copyright (C) 2011 Nadim Benabdenbi <nadim.benabdenbi@gmail.com>
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.google.code.maven.plugin.beans.builder;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.ClassUtils;

import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JType;

/**
 * {@link BuilderSourceGenerator} implementation: Generate builder source using reflection on the bean type.
 * 
 * @author <a href="mailto:nadim.benabdenbi@gmail.com">Nadim Benabdenbi</a>
 * @version 1.0
 * @since JDK1.6
 * 
 * @see BuilderSourceGenerator
 */
public class ReflectionBuilderSourceGenerator extends AbstractBuilderSourceGenerator<Class<?>, Method, Constructor<?>> {
	
	/**
	 * the bean java type
	 */
	private final Class<?> beanJavaType;
	
	/**
	 * constructs a builder generator
	 * 
	 * @param codeModel
	 *            the codeModel
	 * @param javaType
	 *            the javaType
	 * @throws JClassAlreadyExistsException
	 */
	public ReflectionBuilderSourceGenerator(JCodeModel codeModel, Class<?> javaType) throws JClassAlreadyExistsException {
		super(codeModel, javaType);
		this.beanJavaType = javaType;
		
	}
	
	@Override
	protected JClass buildBeanClass(JCodeModel codeModel, Class<?> javaType) {
		getPackage(codeModel, ClassUtils.getPackageName(javaType));
		JClass jClass = codeModel.ref(javaType);
		return jClass;
	}
	
	@Override
	protected List<JClass> getConstructorExceptions(Constructor<?> constructor) {
		List<JClass> exceptions = new ArrayList<JClass>();
		for (Class<?> exception : constructor.getExceptionTypes()) {
			exceptions.add(codeModel.ref(exception));
		}
		return exceptions;
	}
	
	@Override
	protected List<JType> getConstructorParameters(Constructor<?> constructor) {
		List<JType> parameters = new ArrayList<JType>();
		for (Class<?> parameter : constructor.getParameterTypes()) {
			parameters.add(convert(parameter));
		}
		return parameters;
	}
	
	@Override
	protected List<Constructor<?>> getConstructors() {
		return Arrays.asList(beanJavaType.getConstructors());
	}
	
	@Override
	protected boolean isConstructorPublic(Constructor<?> constructor) {
		return Modifier.isPublic(constructor.getModifiers());
	}
	
	@Override
	protected String getMethodName(Method method) {
		return method.getName();
	}
	
	@Override
	protected JType getMethodParameter(Method method) {
		return convert(method.getParameterTypes()[0]);
	}
	
	@Override
	protected List<JClass> getMethodExceptions(Method method) {
		List<JClass> exceptions = new ArrayList<JClass>();
		for (Class<?> exception : method.getExceptionTypes()) {
			exceptions.add(codeModel.ref(exception));
		}
		return exceptions;
	}
	
	@Override
	protected JClass getCollectionGeneric(Method method) {
		if (method.getGenericParameterTypes() != null//
				&& method.getGenericParameterTypes().length == 1 //
				&& method.getGenericParameterTypes()[0] instanceof ParameterizedType//
				&& ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments() != null //
				&& ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments().length == 1) {
			// generic type introspection
			String listGeneric = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0].toString();
			listGeneric = listGeneric.substring(listGeneric.indexOf(' ') + 1);
			return codeModel.ref(listGeneric);
		} else {
			return null;
		}
	}
	
	@Override
	protected List<JClass> getMapGenerics(Method method) {
		if (method.getGenericParameterTypes() != null//
				&& method.getGenericParameterTypes().length == 1 //
				&& method.getGenericParameterTypes()[0] instanceof ParameterizedType//
				&& ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments() != null //
				&& ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments().length == 2) {
			String mapKeyGeneric = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0].toString();
			mapKeyGeneric = mapKeyGeneric.substring(mapKeyGeneric.indexOf(' ') + 1);
			String mapValueGeneric = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[1].toString();
			mapValueGeneric = mapValueGeneric.substring(mapValueGeneric.indexOf(' ') + 1);
			List<JClass> mapGenerics = new ArrayList<JClass>();
			mapGenerics.add(codeModel.ref(mapKeyGeneric));
			mapGenerics.add(codeModel.ref(mapValueGeneric));
			return mapGenerics;
		} else {
			return null;
		}
	}
	
	@Override
	protected List<Method> getMethods() {
		return Arrays.asList(beanJavaType.getMethods());
	}
	
	@Override
	protected int getMethodParameterLength(Method method) {
		return method.getParameterTypes().length;
	}
	
	@Override
	protected boolean isVoid(Method method) {
		return method.getReturnType().getName().equals("void");
	}
	
	@Override
	protected boolean isMethodPublic(Method method) {
		return Modifier.isPublic(method.getModifiers());
	}
}
